CHAPTER 14

Threading

The mere mention of multithreading can strike fear in the hearts of some, while it can fire up others for a good challenge. The fact of the matter is that multithreading, which we’ll call threading from now on, is an area filled with challenges. Threading bugs can be some of the hardest bugs to find, due to their asynchronous nature. In fact, some threading bugs don’t even rear their ugly head until you run your application on a multiprocessor machine, since that’s the only way to get true concurrent multithreading. For this reason, anyone developing a multithreaded application should test on a multiprocessor machine. Otherwise, you run the risk of sending your product out the door with lurking threading bugs.

Threading in VB 2005 and .NET

Even though threading environments have presented many challenges and hurdles, the com-mon language runtime (CLR) and .NET Framework mitigate many of these risks and provide a clean model to build upon. It’s still true that the greatest challenge of creating high-quality threaded code is that of synchronization. The .NET Framework makes it easier than ever to create new threads or utilize a system-managed pool of threads, and it provides intuitive objects that help you synchronize those threads with each other.

Managed threads are virtual threads in the sense that they don’t map one-to-one to OS threads. Managed threads do actually run concurrently, but it would be erroneous to assume that the OS thread currently running a particular managed thread’s code will only run man-aged code for that thread. In fact, an OS thread could run managed code for multiple managed threads in multiple application domains in the current implementation of the CLR. If you burrow down to the OS thread using the P/Invoke layer to make direct Win32 calls, be sure that you only use that information for debugging purposes and base no program logic on it. Otherwise, you’ll end up with something that may break as soon as you run it on another CLR implementation.

It would be erroneous to conclude that multithreaded programming is just about creating extra threads to do something that can take a long time to do. Sure, that’s part of the puzzle. When you create a desktop application, you definitely want to use a threading technique to ensure that the UI stays responsive during a long computational operation, because we all know what impatient users tend to do when desktop applications become unresponsive: they kill them! But it’s important to realize that there is much more to the threading puzzle than creating an extra thread to run some random code. That task is actually quite easy, so let’s take a look and see how easy it really is.

Starting Threads

As we said, creating a thread is simple. Take a look at the following example to see what we mean:

Imports System
Imports System.Threading

Public Class EntryPoint
    Private Shared Sub ThreadFunc()
        Console.WriteLine("Hello from new thread {0}!", Thread.CurrentThread.GetHashCode())
    End Sub

    Shared Sub Main()
        'Create the new thread.
        Dim newThread As Thread = New Thread(AddressOf EntryPoint.ThreadFunc)

        Console.WriteLine("Main Thread is {0}", Thread.CurrentThread.GetHashCode())
        Console.WriteLine("Starting new thread...")

        'Start the new thread.
        newThread.Start()

        'Wait for new thread to finish.
        newThread.Join()

        Console.WriteLine("New thread has finished")
    End Sub
End Class

The previous example displays the following when executed:

Main Thread is 1
Starting new thread...
Hello from new thread 3!
New thread has finished

All you have to do is create a new System.Thread object and pass an instance of the ThreadStart delegate as the parameter to the constructor. The ThreadStart delegate refer-ences a method that takes no parameters and returns no parameters. In the previous example, we chose to use the shared ThreadFuncmethod as the start of execution for the new thread. We could have just as easily chosen to use any other method visible to the code creating the thread that neither accepted nor returned parameters. Notice that the code also outputs the hash code from the thread, using GetHashCode(), to demonstrate how you identify threads in the managed world. As long as this thread is alive, it is guaranteed never to collide with any other thread in any other application domain of this process. The thread hash code is not globally unique on the entire system. Also, you can see how you can get a reference to the cur-rent thread by accessing the shared property Thread.CurrentThread. Finally, notice the call to the Join method on the newThread object. In this case, the code waits forever for the thread to finish. Thread.Join() also provides a few overloads that allow you to specify a time-out period on the wait.